移动开发

ConnectivityManager.bindProcessToNetwork

下面给出尽可能详尽的说明:ConnectivityManager.bindProcessToNetwork 的语义、API 层级、与旧 API 的差别、典型用法(含 Kotlin 示例)、常见坑与调试建议,以及更安全/可控的替代方案(按 socket 绑定或使用 NetworkSocketFactory)。我会把关键事实用官方/权威来源标注出来,方便你查阅。


概览 — bindProcessToNetwork 是什么(核心含义)

  • ConnectivityManager.bindProcessToNetwork(network)当前进程(process)绑定到指定的 Network 对象。
    一旦成功绑定:此进程中新创建的所有 socket(除非显式用 network 提供的 SocketFactory/手动绑定)都将走这个 network;并且该进程的域名解析(hostname resolution)也会被限制在该 network 的 DNS 上。如果传 null,则清除绑定,恢复系统默认路由。(IUT de Sénart/Fontainebleau)
  • API 层级:在 API level 23(Android 6.0 / Marshmallow)加入。在 Lollipop(API 21)有旧的 setProcessDefaultNetwork,从 M 以后用 bindProcessToNetwork。NDK 对应的底层接口也在 23 引入。(Android Git Repositories)
  • 返回值:布尔型。true 表示绑定成功;false 通常表示 Network 已不可用(或缺权限、其他失败原因)。(IUT de Sénart/Fontainebleau)

为什么/何时用它(典型场景)

  • 当设备同时有多个网络连接(例如:Wi-Fi(但无互联网)+ 蜂窝数据)且你希望把整个 App(进程) 的流量强制路由到某个特定网络(例如连接到不通 Internet 的 IoT 设备的 Wi-Fi),可以使用它来保证网络请求不被系统路由到默认有 Internet 的网络。常见于:局域网设备通信(摄像头、路由器配置页、设备配网)或把流量强制通过 VPN/专用网络进行测试。(Stack Overflow)

推荐使用模式(原则性建议)

  1. 不要盲目在任意时刻调用 —— 常见做法是:ConnectivityManager.requestNetworkNetworkRequest → 在 NetworkCallback.onAvailable(network) 被回调时再调用 bindProcessToNetwork(network)。在 onLost() 中清除绑定(传 null)。这种模式在官方示例与文档中推荐。(Android Developers)
  2. 优先考虑按 socket 绑定或使用 Network.getSocketFactory(),而不是进程级绑定。文档明确建议:如果只需要部分流量走特定网络,优先使用单个 Socket 的绑定(Network.bindSocket() / Network.getSocketFactory() / Network.getAllByName(),这是更精细、更安全的方案;bindProcessToNetwork 是粗粒度(影响整个进程),且当 network 断开时所有通过该绑定创建的 sockets 都会失败。(IUT de Sénart/Fontainebleau)

Permissions / 权限 / 行为差异

  • 调用这类网络请求/绑定相关的 API 时,常见问题来自缺少必要的 Manifest 权限:
    • android.permission.INTERNET:进行网络操作时必需。没有这个权限,绑定会失败(或返回 false)。(Android Developers Blog)
    • 请求/修改网络状态(例如 requestNetwork / 改变网络状态)在某些 Android 版本或情况下需要 CHANGE_NETWORK_STATE(以及针对某些 OEM/Android 版本的行为差异)。实务中如果遇到 onAvailable 不触发或绑定失败,应检查这些权限与运行时条件。(Stack Overflow)
  • VPN / 平台限制:正在运行的 VPN、某些 OEM ROM(厂商定制)或系统策略可以影响绑定能否成功或流量走向(有时 bindProcessToNetwork 返回 false 或行为异常)。不少社区讨论/issue 提到 VPN 会阻止或影响绑定。遇到异常设备时要验证是否与 VPN/ROM 有关。(Qiita)

典型 Kotlin 使用范例(完整流程)

下面给出一个推荐流程(检查 API level、构造 NetworkRequest、注册回调、在 onAvailable 绑定、在 onLost 清理)。代码尽量简洁并包含关键注释——按你之前偏好我用 Kotlin(传统视图/非 Compose)。

// 在 Activity/Service/某个 Manager 中
private val cm: ConnectivityManager by lazy {
    applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}

private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        // 在回调中绑定进程到该 Network(必须在 onAvailable 中做)
        val success = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            cm.bindProcessToNetwork(network) // API 23+
        } else {
            // Lollipop 上使用旧 API(如果需要兼容)
            @Suppress("DEPRECATION")
            cm.setProcessDefaultNetwork(network)
        }
        Log.i("NetBind", "bindProcessToNetwork -> $success")
        // 额外:若你只想对部分请求生效,请考虑不要在这里做进程绑定,
        // 而是使用 network.getSocketFactory() 或 network.bindSocket(socket)
    }

    override fun onLost(network: Network) {
        // 如果绑定的是同一个 network,应当清除绑定(传 null)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            cm.bindProcessToNetwork(null)
        } else {
            @Suppress("DEPRECATION")
            cm.setProcessDefaultNetwork(null)
        }
        Log.i("NetBind", "network lost -> cleared process binding")
    }
}

fun requestSpecificNetwork() {
    // 例如:我们想要 Wi-Fi 类型且带某些能力的网络(按需修改)
    val req = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        // .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) // 视情况而定
        .build()
    cm.requestNetwork(req, networkCallback)
}

// 调用时也要记得在适当时机 unregisterNetworkCallback
fun stopRequesting() {
    cm.unregisterNetworkCallback(networkCallback)
    // 并确保已经清除 bind(上面的 onLost 也会做)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        cm.bindProcessToNetwork(null)
    } else {
        @Suppress("DEPRECATION")
        cm.setProcessDefaultNetwork(null)
    }
}

要点:

  • 必须onAvailable() 中去绑定;在回调外调用可能失败(因为 network 未就绪)。(Qiita)
  • requestNetwork 需要 NetworkCallback,并注意超时 / onUnavailable() 的处理(不同 Android 版本在超时行为上存在差异)。(Medium)

更细粒度的替代:按 Socket 或使用 Network.getSocketFactory()

当你只想把某部分流量发到特定 network(例如仅连接局域网设备,不影响其它系统请求)时,优先使用这种方式:

  • Network.getSocketFactory():通过它创建 socket 或构造 OkHttp / HTTP 客户端使用的 SocketFactory,让该 socket 走指定网络。
  • Network.bindSocket(socket):把已创建的 socket 绑定到该 Network。
  • Network.getAllByName(hostname):在该网络的 DNS 上解析 hostname(文档提到 getAllByName 优于全局解析)。(Microsoft Learn)

简单示例(手工 socket -> 发起 HTTP GET,示意):

// 假设 network 是 onAvailable 中提供的 Network
val socket = Socket()
network.bindSocket(socket) // 将 socket 绑定到这个 network
socket.connect(InetSocketAddress("192.168.4.1", 80), 5000)

// 之后可以通过 socket.getInputStream()/getOutputStream() 自行发送 HTTP 请求
// (或者使用 network.getSocketFactory().createSocket() 的方式)

优点:不会影响整个进程,也更容易在 network 断开时局部恢复。官方文档建议在可能时优先使用这种方式。(IUT de Sénart/Fontainebleau)


DNS 与解析行为

  • 绑定进程后,进程级的 host name 解析将被限制到该 Network 的 DNS(这也是为什么整个进程绑定后,DNS 查询可能失败或解析不同)。若只想在某个 network 上解析某个 host,优先使用 Network.getAllByName()(针对 network 的解析接口)。(Android Git Repositories)

进程绑定的风险 / 常见坑(实践经验与社区问题)

  • 网络断开风险:如果你把进程绑定到一个刚好没有 Internet 的 Wi-Fi,且那个 network 断开/不可用,所有通过该绑定创建的 sockets 都会“停止工作”,DNS 解析会失败。必须在 onLost 中清理或实现重试逻辑。(IUT de Sénart/Fontainebleau)
  • 必须在回调内绑定:很多人把 bindProcessToNetwork 放在没有网络的时刻调用,导致返回 false。正确做法是在 NetworkCallback.onAvailable 中调用。(Qiita)
  • 权限/厂商差异:某些机型/ROM 有 bug 或强制行为(例如在连接到某些 Wi-Fi 时系统会立刻切换到蜂窝并断 Wi-Fi),以及 VPN 会影响绑定,遇到奇怪问题要在多台机(含 Pixel)上验证。社区讨论与 issue 很多。(GitHub)
  • 旧 API 的兼容:Lollipop 有 setProcessDefaultNetwork(deprecated),在 M(API23)后使用 bindProcessToNetwork。如果要兼容旧设备需写分支。(Android Developers Blog)

调试技巧

  1. onAvailable/onLost 打 log(打印 NetworktoString()network.hashCode() 等),确认回调何时触发。(Android Developers)
  2. getBoundNetworkForProcess()(API 23+)检查当前进程是否真的绑定到了某个 network(返回 Network?)。(注意:有版本差异)(Android Git Repositories)
  3. 如果 socket 失败,确认是否因为 DNS 解析失败(尝试用 network.getAllByName() 在回调中解析域名来验证)。(Android Git Repositories)
  4. 测试时尽量在没有 VPN 的干扰下验证;若用户设备有节流/省电策略也会导致奇怪行为。(Qiita)

何时不要用 bindProcessToNetwork

  • 你只需要对单个连接或少量连接控制路由时,不要使用进程绑定(用 socket 绑定或 socketFactory)。
  • 不要把整个应用长期绑定到一个没有 Internet 的 Wi-Fi(除非你知道所有行为并在 lost 时可靠清理),因为会影响第三方 SDK、后台任务等。(IUT de Sénart/Fontainebleau)

参考与重要出处(便于深入阅读)

  1. Android 官方 Connectivity 文档与网络操作指南(NetworkRequest / 绑定示例):Android Developers(Connectivity / reading-network-state / example)。(Android Developers)
  2. 官方博客示例:Connecting your App to a Wi-Fi Device(讲解 setProcessDefaultNetworkbindProcessToNetwork 的过渡与示例)。(Android Developers Blog)
  3. API reference(说明 bindProcessToNetwork 行为、何时清除绑定与返回含义,已标注添加 API level):各种 API 文档条目(ConnectivityManager 参考)。(IUT de Sénart/Fontainebleau)
  4. NDK / 底层接口说明(表明该功能在 API 23 引入,及对应的底层函数):NDK networking reference。(Android Developers)
  5. 社区讨论 / 常见坑(StackOverflow / Qiita / GitHub issues),说明 VPN/某些机型与回调时机等实务问题。(Stack Overflow)

快速总结(要点)

  • bindProcessToNetwork(network):把整个进程绑定到 network,影响新建 sockets 与 DNS。API 23+。(IUT de Sénart/Fontainebleau)
  • 推荐流程:requestNetwork → 在 onAvailablebindProcessToNetwork(network) → 在 onLostbindProcessToNetwork(null);补充处理 onUnavailable/超时。(Android Developers)
  • 若只需部分流量走特定网络,优先使用 Network.getSocketFactory()Network.bindSocket()(更细粒度,风险更小)。(Microsoft Learn)
  • 注意权限、VPN、OEM 差异与网络断开时的资源清理。(Stack Overflow)

留言

您的邮箱地址不会被公开。 必填项已用 * 标注